From: Florian Müllner Date: Thu, 28 Jul 2011 14:59:03 +0000 (+0200) Subject: user-menu: Implement new mockups X-Git-Tag: archive/raspbian/50.5-1+rpi1~1^2~1013^2^2~7853 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=0751a90bd93d89782f1a711b360fd54dad0651f6;p=gnome-shell.git user-menu: Implement new mockups The current user status menu allow to set the session status, which also influences the IM status when signed in with mission-control. However, the way it is presented to the user makes it hard to figure out how the statuses interact or that there are two distinct status in the first place. Therefore, use a separate control for each status, and update the overall look to match gnome-contacts. https://bugzilla.gnome.org/show_bug.cgi?id=652837 --- diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index fa2b0b777..313d0b232 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -387,6 +387,49 @@ StTooltip StLabel { spacing: 4px; } +.status-chooser { + spacing: .4em; +} + +.status-chooser .popup-menu-item, +.status-chooser-combo .popup-menu-item { + padding: .4em; +} + +.status-chooser-user-icon { + border: 2px solid #8b8b8b; + border-radius: 5px; + width: 48pt; + height: 48pt; +} + +.status-chooser-user-icon:hover { + border: 2px solid #bbbbbb; +} + +.status-chooser-user-name { + font-weight: bold; + font-size: 1.3em; +} + +.status-chooser-combo { + border: 1px solid transparent; +} + +.status-chooser-combo.popup-combo-menu { + background-color: rgba(0,0,0,0.7); + padding: .4em 0em; + border-radius: 4px; + border: 1px solid #5f5f5f; + color: #ffffff; + font-size: 10.5pt; +} + +.status-chooser-status-item, +.status-chooser-combo > .popup-combobox-item { + spacing: .4em; +} + #legacyTray { spacing: 14px; padding-left: 14px; diff --git a/js/ui/userMenu.js b/js/ui/userMenu.js index 0d53b54ea..b9f9f16c3 100644 --- a/js/ui/userMenu.js +++ b/js/ui/userMenu.js @@ -22,11 +22,337 @@ const DISABLE_USER_SWITCH_KEY = 'disable-user-switching'; const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen'; const DISABLE_LOG_OUT_KEY = 'disable-log-out'; +const WRAP_WIDTH = 150; +const DIALOG_ICON_SIZE = 64; + +const IMStatus = { + AVAILABLE: 0, + BUSY: 1, + HIDDEN: 2, + AWAY: 3, + IDLE: 4, + OFFLINE: 5, + LAST: 6 +}; + // Adapted from gdm/gui/user-switch-applet/applet.c // // Copyright (C) 2004-2005 James M. Cape . // Copyright (C) 2008,2009 Red Hat, Inc. + +function IMStatusItem(label, iconName) { + this._init(label, iconName); +} + +IMStatusItem.prototype = { + __proto__: PopupMenu.PopupBaseMenuItem.prototype, + + _init: function(label, iconName) { + PopupMenu.PopupBaseMenuItem.prototype._init.call(this); + + this.actor.add_style_class_name('status-chooser-status-item'); + + this._icon = new St.Icon({ style_class: 'popup-menu-icon' }); + this.addActor(this._icon); + + if (iconName) + this._icon.icon_name = iconName; + + this.label = new St.Label({ text: label }); + this.addActor(this.label); + } +}; + +function IMUserNameItem() { + this._init(); +} + +IMUserNameItem.prototype = { + __proto__: PopupMenu.PopupBaseMenuItem.prototype, + + _init: function() { + PopupMenu.PopupBaseMenuItem.prototype._init.call(this, + { reactive: false, + style_class: 'status-chooser-user-name' }); + + this._wrapper = new Shell.GenericContainer(); + this._wrapper.connect('get-preferred-width', + Lang.bind(this, this._wrapperGetPreferredWidth)); + this._wrapper.connect('get-preferred-height', + Lang.bind(this, this._wrapperGetPreferredHeight)); + this._wrapper.connect('allocate', + Lang.bind(this, this._wrapperAllocate)); + this.addActor(this._wrapper, { expand: true, span: -1 }); + + this.label = new St.Label(); + this.label.clutter_text.set_line_wrap(true); + this._wrapper.add_actor(this.label); + }, + + _wrapperGetPreferredWidth: function(actor, forHeight, alloc) { + [alloc.min_size, alloc.natural_size] = this.label.get_preferred_width(-1); + if (alloc.natural_size > WRAP_WIDTH) + alloc.natural_size = WRAP_WIDTH; + }, + + _wrapperGetPreferredHeight: function(actor, forWidth, alloc) { + let minWidth, natWidth; + [alloc.min_size, alloc.natural_size] = this.label.get_preferred_height(forWidth); + [minWidth, natWidth] = this.label.get_preferred_width(-1); + if (natWidth > WRAP_WIDTH) { + alloc.min_size *= 2; + alloc.natural_size *= 2; + } + }, + + _wrapperAllocate: function(actor, box, flags) { + let availWidth = box.x2 - box.x1; + let availHeight = box.y2 - box.y1; + this.label.allocate(box, flags); + } +}; + +function IMStatusChooserItem() { + this._init(); +} + +IMStatusChooserItem.prototype = { + __proto__: PopupMenu.PopupBaseMenuItem.prototype, + + _init: function() { + PopupMenu.PopupBaseMenuItem.prototype._init.call (this, + { reactive: false, + style_class: 'status-chooser' }); + + this._iconBin = new St.Button({ style_class: 'status-chooser-user-icon' }); + this.addActor(this._iconBin); + + this._iconBin.connect('clicked', Lang.bind(this, + function() { + this.activate(); + })); + + this._section = new PopupMenu.PopupMenuSection(); + this.addActor(this._section.actor); + + this._name = new IMUserNameItem(); + this._section.addMenuItem(this._name); + + this._combo = new PopupMenu.PopupComboBoxMenuItem({ style_class: 'status-chooser-combo' }); + this._section.addMenuItem(this._combo); + + let item; + + item = new IMStatusItem(_("Available"), 'user-available'); + this._combo.addMenuItem(item, IMStatus.AVAILABLE); + + item = new IMStatusItem(_("Busy"), 'user-busy'); + this._combo.addMenuItem(item, IMStatus.BUSY); + + item = new IMStatusItem(_("Hidden"), 'user-invisible'); + this._combo.addMenuItem(item, IMStatus.HIDDEN); + + item = new IMStatusItem(_("Away"), 'user-away'); + this._combo.addMenuItem(item, IMStatus.AWAY); + + item = new IMStatusItem(_("Idle"), 'user-idle'); + this._combo.addMenuItem(item, IMStatus.IDLE); + + item = new IMStatusItem(_("Unavailable"), 'user-offline'); + this._combo.addMenuItem(item, IMStatus.OFFLINE); + + this._combo.connect('active-item-changed', + Lang.bind(this, this._changeIMStatus)); + + this._presence = new GnomeSession.Presence(); + this._presence.getStatus(Lang.bind(this, this._sessionStatusChanged)); + this._presence.connect('StatusChanged', + Lang.bind(this, this._sessionStatusChanged)); + + this._previousPresence = undefined; + + this._accountMgr = Tp.AccountManager.dup() + this._accountMgr.connect('most-available-presence-changed', + Lang.bind(this, this._IMStatusChanged)); + this._accountMgr.prepare_async(null, Lang.bind(this, + function(mgr) { + let [presence, s, msg] = mgr.get_most_available_presence(); + + this._previousPresence = presence; + this._IMStatusChanged(mgr, presence, s, msg); + })); + + this._gdm = Gdm.UserManager.ref_default(); + this._gdm.queue_load(); + + this._user = this._gdm.get_user(GLib.get_user_name()); + + this._userLoadedId = this._user.connect('notify::is-loaded', + Lang.bind(this, + this._updateUser)); + this._userChangedId = this._user.connect('changed', + Lang.bind(this, + this._updateUser)); + }, + + // Override getColumnWidths()/setColumnWidths() to make the item + // independent from the overall column layout of the menu + getColumnWidths: function() { + return []; + }, + + setColumnWidths: function(widths) { + this._columnWidths = PopupMenu.PopupBaseMenuItem.prototype.getColumnWidths.call(this); + let sectionWidths = this._section.getColumnWidths(); + this._section.setColumnWidths(sectionWidths); + }, + + _updateUser: function() { + let iconFile = null; + if (this._user.is_loaded) { + this._name.label.set_text(this._user.get_real_name()); + iconFile = this._user.get_icon_file(); + if (!GLib.file_test(iconFile, GLib.FileTest.EXISTS)) + iconFile = null; + } else { + this._name.label.set_text(""); + } + + if (iconFile) + this._setIconFromFile(iconFile); + else + this._setIconFromName('avatar-default'); + }, + + _setIconFromFile: function(iconFile) { + this._iconBin.set_style('background-image: url("' + iconFile + '");'); + }, + + _setIconFromName: function(iconName) { + this._iconBin.set_style(null); + + if (iconName != null) { + let textureCache = St.TextureCache.get_default(); + let icon = textureCache.load_icon_name(this._iconBin.get_theme_node(), + iconName, + St.IconType.SYMBOLIC, + DIALOG_ICON_SIZE); + + this._iconBin.child = icon; + this._iconBin.show(); + } else { + this._iconBin.child = null; + this._iconBin.hide(); + } + }, + + _statusForPresence: function(presence) { + switch(presence) { + case Tp.ConnectionPresenceType.AVAILABLE: + return _("Available"); + case Tp.ConnectionPresenceType.BUSY: + return _("Busy"); + case Tp.ConnectionPresenceType.OFFLINE: + return _("Unavailable"); + case Tp.ConnectionPresenceType.HIDDEN: + return _("Hidden"); + case Tp.ConnectionPresenceType.AWAY: + return _("Away"); + case Tp.ConnectionPresenceType.EXTENDED_AWAY: + return _("Idle"); + default: + return _("Unknown"); + } + }, + + _IMStatusChanged: function(accountMgr, presence, status, message) { + if (presence == Tp.ConnectionPresenceType.AVAILABLE) + this._presence.setStatus(GnomeSession.PresenceStatus.AVAILABLE); + + if (!this._expectedPresence || presence != this._expectedPresence) + this._previousPresence = presence; + else + this._expectedPresence = undefined; + + let activatedItem; + + if (presence == Tp.ConnectionPresenceType.AVAILABLE) + activatedItem = IMStatus.AVAILABLE; + else if (presence == Tp.ConnectionPresenceType.BUSY) + activatedItem = IMStatus.BUSY; + else if (presence == Tp.ConnectionPresenceType.HIDDEN) + activatedItem = IMStatus.HIDDEN; + else if (presence == Tp.ConnectionPresenceType.AWAY) + activatedItem = IMStatus.AWAY; + else if (presence == Tp.ConnectionPresenceType.EXTENDED_AWAY) + activatedItem = IMStatus.IDLE; + else + activatedItem = IMStatus.OFFLINE; + + this._combo.setActiveItem(activatedItem); + for (let i = 0; i < IMStatus.LAST; i++) { + if (i == IMStatus.AVAILABLE || i == IMStatus.OFFLINE) + continue; // always visible + + this._combo.setItemVisible(i, i == activatedItem); + } + }, + + _changeIMStatus: function(menuItem, id) { + let [presence, s, msg] = this._accountMgr.get_most_available_presence(); + let newPresence, status; + + if (id == IMStatus.AVAILABLE) { + newPresence = Tp.ConnectionPresenceType.AVAILABLE; + } else if (id == IMStatus.OFFLINE) { + newPresence = Tp.ConnectionPresenceType.OFFLINE; + } else + return; + + status = this._statusForPresence(newPresence); + msg = msg ? msg : ""; + this._accountMgr.set_all_requested_presences(newPresence, status, msg); + }, + + _sessionStatusChanged: function(sessionPresence, sessionStatus) { + let [presence, s, msg] = this._accountMgr.get_most_available_presence(); + let newPresence, status; + + if (sessionStatus == GnomeSession.PresenceStatus.AVAILABLE) { + newPresence = this._previousPresence; + } else if (sessionStatus == GnomeSession.PresenceStatus.BUSY) { + // Only change presence if the current one is "more present" than + // busy, or if coming back from idle + if (presence == Tp.ConnectionPresenceType.AVAILABLE || + presence == Tp.ConnectionPresenceType.EXTENDED_AWAY) { + newPresence = Tp.ConnectionPresenceType.BUSY; + } else { + return; + } + } else if (sessionStatus == GnomeSession.PresenceStatus.IDLE) { + // Only change presence if the current one is "more present" than + // idle + if (presence != Tp.ConnectionPresenceType.OFFLINE) + newPresence = Tp.ConnectionPresenceType.EXTENDED_AWAY; + else + return; + } else { + return; + } + + if (newPresence == undefined) + return; + + status = this._statusForPresence(newPresence); + msg = msg ? msg : ""; + + this._expectedPresence = newPresence; + this._accountMgr.set_all_requested_presences(newPresence, status, msg); + } +}; + + function UserMenuButton() { this._init(); } @@ -46,7 +372,6 @@ UserMenuButton.prototype = { this._user = this._gdm.get_user(GLib.get_user_name()); this._presence = new GnomeSession.Presence(); - this._presenceItems = {}; this._session = new GnomeSession.SessionManager(); this._haveShutdown = true; @@ -60,13 +385,30 @@ UserMenuButton.prototype = { box.add(this._iconBox, { y_align: St.Align.MIDDLE, y_fill: false }); let textureCache = St.TextureCache.get_default(); - this._availableIcon = new St.Icon({ icon_name: 'user-available', style_class: 'popup-menu-icon' }); - this._busyIcon = new St.Icon({ icon_name: 'user-busy', style_class: 'popup-menu-icon' }); - this._invisibleIcon = new St.Icon({ icon_name: 'user-invisible', style_class: 'popup-menu-icon' }); - this._idleIcon = new St.Icon({ icon_name: 'user-idle', style_class: 'popup-menu-icon' }); - - this._presence.connect('StatusChanged', Lang.bind(this, this._updatePresenceIcon)); - this._presence.getStatus(Lang.bind(this, this._updatePresenceIcon)); + this._offlineIcon = new St.Icon({ icon_name: 'user-offline', + style_class: 'popup-menu-icon' }); + this._availableIcon = new St.Icon({ icon_name: 'user-available', + style_class: 'popup-menu-icon' }); + this._busyIcon = new St.Icon({ icon_name: 'user-busy', + style_class: 'popup-menu-icon' }); + this._invisibleIcon = new St.Icon({ icon_name: 'user-invisible', + style_class: 'popup-menu-icon' }); + this._awayIcon = new St.Icon({ icon_name: 'user-away', + style_class: 'popup-menu-icon' }); + this._idleIcon = new St.Icon({ icon_name: 'user-idle', + style_class: 'popup-menu-icon' }); + + this._presence.connect('StatusChanged', + Lang.bind(this, this._updateSwitch)); + this._presence.getStatus(Lang.bind(this, this._updateSwitch)); + + this._account_mgr.connect('most-available-presence-changed', + Lang.bind(this, this._updatePresenceIcon)); + this._account_mgr.prepare_async(null, Lang.bind(this, + function(mgr) { + let [presence, s, msg] = mgr.get_most_available_presence(); + this._updatePresenceIcon(mgr, presence, s, msg); + })); this._name = new St.Label(); box.add(this._name, { y_align: St.Align.MIDDLE, y_fill: false }); @@ -110,9 +452,9 @@ UserMenuButton.prototype = { _updateUserName: function() { if (this._user.is_loaded) - this._name.set_text(this._user.get_real_name()); + this._name.set_text(this._user.get_real_name()); else - this._name.set_text(""); + this._name.set_text(""); }, _updateSwitchUser: function() { @@ -171,38 +513,43 @@ UserMenuButton.prototype = { } }, - _updatePresenceIcon: function(presence, status) { - if (status == GnomeSession.PresenceStatus.AVAILABLE) + _updateSwitch: function(presence, status) { + let active = status == GnomeSession.PresenceStatus.BUSY; + this._dontDisturbSwitch.setToggleState(active); + }, + + _updatePresenceIcon: function(accountMgr, presence, status, message) { + if (presence == Tp.ConnectionPresenceType.AVAILABLE) this._iconBox.child = this._availableIcon; - else if (status == GnomeSession.PresenceStatus.BUSY) + else if (presence == Tp.ConnectionPresenceType.BUSY) this._iconBox.child = this._busyIcon; - else if (status == GnomeSession.PresenceStatus.INVISIBLE) + else if (presence == Tp.ConnectionPresenceType.HIDDEN) this._iconBox.child = this._invisibleIcon; - else + else if (presence == Tp.ConnectionPresenceType.AWAY) + this._iconBox.child = this._awayIcon; + else if (presence == Tp.ConnectionPresenceType.EXTENDED_AWAY) this._iconBox.child = this._idleIcon; - - for (let itemStatus in this._presenceItems) - this._presenceItems[itemStatus].setShowDot(itemStatus == status); + else + this._iconBox.child = this._offlineIcon; }, _createSubMenu: function() { let item; - item = new PopupMenu.PopupImageMenuItem(_("Available"), 'user-available'); - item.connect('activate', Lang.bind(this, this._setPresenceStatus, GnomeSession.PresenceStatus.AVAILABLE)); + item = new IMStatusChooserItem(); + item.connect('activate', Lang.bind(this, this._onMyAccountActivate)); this.menu.addMenuItem(item); - this._presenceItems[GnomeSession.PresenceStatus.AVAILABLE] = item; - item = new PopupMenu.PopupImageMenuItem(_("Busy"), 'user-busy'); - item.connect('activate', Lang.bind(this, this._setPresenceStatus, GnomeSession.PresenceStatus.BUSY)); + item = new PopupMenu.PopupSwitchMenuItem(_("Do Not Disturb")); + item.connect('activate', Lang.bind(this, this._updatePresenceStatus)); this.menu.addMenuItem(item); - this._presenceItems[GnomeSession.PresenceStatus.BUSY] = item; + this._dontDisturbSwitch = item; item = new PopupMenu.PopupSeparatorMenuItem(); this.menu.addMenuItem(item); - item = new PopupMenu.PopupMenuItem(_("My Account")); - item.connect('activate', Lang.bind(this, this._onMyAccountActivate)); + item = new PopupMenu.PopupMenuItem(_("Online Accounts")); + item.connect('activate', Lang.bind(this, this._onOnlineAccountsActivate)); this.menu.addMenuItem(item); item = new PopupMenu.PopupMenuItem(_("System Settings")); @@ -238,10 +585,10 @@ UserMenuButton.prototype = { this._updateSuspendOrPowerOff(); }, - _setPresenceStatus: function(item, event, status) { + _updatePresenceStatus: function(item, event) { + let status = item.state ? GnomeSession.PresenceStatus.BUSY + : GnomeSession.PresenceStatus.AVAILABLE; this._presence.setStatus(status); - - this._setIMStatus(status); }, _onMyAccountActivate: function() { @@ -250,6 +597,12 @@ UserMenuButton.prototype = { app.activate(); }, + _onOnlineAccountsActivate: function() { + Main.overview.hide(); + let app = Shell.AppSystem.get_default().lookup_setting('gnome-online-accounts-panel.desktop'); + app.activate(-1); + }, + _onPreferencesActivate: function() { Main.overview.hide(); let app = Shell.AppSystem.get_default().lookup_app('gnome-control-center.desktop'); @@ -287,31 +640,5 @@ UserMenuButton.prototype = { } else { this._session.ShutdownRemote(); } - }, - - _setIMStatus: function(session_status) { - let [presence_type, presence_status, msg] = this._account_mgr.get_most_available_presence(); - let type, status; - - // We change the IM presence only if there are connected accounts - if (presence_type == Tp.ConnectionPresenceType.UNSET || - presence_type == Tp.ConnectionPresenceType.OFFLINE || - presence_type == Tp.ConnectionPresenceType.UNKNOWN || - presence_type == Tp.ConnectionPresenceType.ERROR) - return; - - if (session_status == GnomeSession.PresenceStatus.AVAILABLE) { - type = Tp.ConnectionPresenceType.AVAILABLE; - status = "available"; - } - else if (session_status == GnomeSession.PresenceStatus.BUSY) { - type = Tp.ConnectionPresenceType.BUSY; - status = "busy"; - } - else { - return; - } - - this._account_mgr.set_all_requested_presences(type, status, msg); } };